#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdint.h>

#include "Estructuras.h"
#include "includes/Mensajes.h"
#include "includes/Conexiones.h"


static struct epoll_event *events;
stRaidData raidDataGlobal;



int main(void)
{
	int res = -1;
	int n, i;
	int client_fd = -1;
	char buf[100];
	int nbytes, iResult;
	
	struct sockaddr_in clientaddr;

	/* Leer archivo de configuracion*/
	levantarConfig();

	/* Armar y cargar estructuras*/
	cargarDatos();

	/* Levantar Consola*/
	iniciarConsola();

	/*Iniciar como Server */
	iniciarComoServer();
		
	printf("*****Iniciando Server *******\n");

	for(;;) /* A la espera de conexiones FS/PPD - pedidos READ/WRITE - mensajes HandShake - Se�ales */
	{
		/* Espero por los eventos en los socket
		epoll_wait() = Wait for events on the epoll file descriptor epfd for a maximum time of timeout milliseconds. */
		
		printf("epoll_wait(). socket en listen %d\n", raidDataGlobal.listener);
		
		n = epoll_wait(raidDataGlobal.epfd, events, MAX_CON, -1);
		
		
		for (i = 0; i < n; i++) 
		{
			client_fd = (int) events[i].data.fd;

			if(client_fd == raidDataGlobal.listener)
			{	
				/* Si hubo actividad en el listener => es un FS/PPD que se me conecta => acepto la conexion */
				aceptarConexion();
				break;
			}
			if (events[i].events & EPOLLHUP)
			{
				/* EPOLLHUP = si el socket se cerro, se cayo la conexion o similar cierro socket, saco fd del pool, etc */
				cerrarOperaciones(client_fd);
				break;
			}
			if (events[i].events & EPOLLIN)
			{
				if(client_fd == raidDataGlobal.sfd_ReplicarDatos)
				{
					/* Si el socket que tuvo actividad es el que enmascara la se�al de r�plica de datos .. */
					/* TODO ver si se puede mejor hacer asi: capturar waitpid() la finalizacion del thread y ahi
					 * invocar a esta funcion. Porque si un thread finaliza es porque su PPD se cayo. */
					atenderSignalReplicarDatos();
					break;
				}

				if(client_fd == raidDataGlobal.sfd_EspejarDisco)
				{
					/* Si el socket que tuvo actividad es el que enmascara la se�al para espejar .. */
					atenderSignalEspejar();
					break;
				}

				/* Si es un pedido de READ/WRITE o un msj HandShake => lo recibo */
				iResult = recepcionarPedido(client_fd);
				if(iResult == HANDSHAKE_REFUSED)
					cerrarOperaciones(client_fd);

				break;
			}
		}
	}
	return EXIT_SUCCESS;
}




int recepcionarPedido(int client_fd)
{
	stMsgNIPC mensaje;
	stMsgNIPC head;
	int iResult;
	
	/* if(pedido = malloc(sizeof(stMsgNIPC)) == NULL) return EXIT_ERROR_MEMORY; */	
	if((mensaje.Payload = malloc(sizeof(uint32_t))) == NULL)
		return EXIT_ERROR_MEMORY;
	
	
	iResult = recibirMensaje(client_fd, &mensaje);
	if(iResult == SOCKET_ERROR_DESCONEXION)
	{
		printf("socket %d hung up\n", client_fd);
		if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_DEL, client_fd, &(raidDataGlobal.ev)) < 0) 
		{
			perror("epoll_ctl");
		}
		close(client_fd);
	}

	if(iResult < 0)
	{
		printf("recv() error! %d", client_fd);
		if (epoll_ctl(raidDataGlobal.epfd, EPOLL_CTL_DEL, client_fd, &(raidDataGlobal.ev)) < 0) 
		{
			perror("epoll_ctl");
		}
		close(client_fd);
	}

	switch (mensaje.PayloadDescriptor)
	{
		case MSG_HANDSHAKE:
			iResult = atenderHandShake(&mensaje, client_fd);
			if(iResult == HANDSHAKE_ERROR) 
				return HANDSHAKE_ERROR;
			if(iResult == HANDSHAKE_REFUSED)
				return HANDSHAKE_REFUSED;
			break;

		case MSG_GET_BOOT_SECTOR | MSG_GET_SECTOR:
			atenderPedidoDeLectura(mensaje.Payload);
			break;

		case MSG_WRITE_SECTOR:
			atenderPedidoDeEscritura(mensaje.Payload);
			break;

		default:
			printf("Saliendo por default, el mensaje no es READ/WRITE ni HANDSHAKE\n");
			break;
	}
	
	return EXIT_SUCCESS;
}

int atenderPedidoDeEscritura(stPedido *pedido)
{
	/* Recorro la lista de threads y por cada thread inserto en su QueueWrite el pedido
	 * y si su state == OPERATIVO envio se�al*
	 */

	collection_list_iterator_threads(raidDataGlobal.listadoThreads, pedido);

	return EXIT_SUCCESS;
}

int atenderPedidoDeLectura(stPedido *pedido)
{
	stThreadPPD *ptrThread;

	/* Busco en mi lista de Threads el siguiente al ultimo usado */
	ptrThread = (stThreadPPD*)collection_list_find_next(raidDataGlobal.listadoThreads, raidDataGlobal.UltimoTIDUsadoForRead);

	/* Actualizo el valor del ultimo usado */
	raidDataGlobal.UltimoTIDUsadoForRead = ptrThread->TID_thread;

	/* Copio el pedido a su QueueRead */
	collection_list_add(ptrThread->QueueReads, pedido);

	/* Si no esta espejando, Envio signal*/
	if(ptrThread->state == STATE_OPERATIVO)
		kill(ptrThread->TID_thread, ptrThread->signal);

	return EXIT_SUCCESS;
}

int atenderSignalReplicarDatos(pthread_t tidThread)
{
	stThreadPPD *threadFinded, *threadNext;
	int i, j, sizeQueueRead, sizeListThreads;
	stPedido *pedido;

	/* Si estoy aca es porque se cayo un PPD y tengo q recuperar sus lecturas */

	/* Busco el thread que me viene por parametro en mi listado */
	int findThread(void* data)
	{
		stThreadPPD* threadNode = (stThreadPPD*)data;
		return (threadNode->TID_thread == tidThread);
	}
	threadFinded = (stThreadPPD*)collection_list_find(raidDataGlobal.listadoThreads, findThread);

	/* Tengo que agregar cada nodo de su QueueRead a las colas de los otros threads */

	sizeQueueRead = collection_list_size(threadFinded->QueueReads);
	sizeListThreads = collection_list_size(raidDataGlobal.listadoThreads);

	for(i = 0; i < sizeQueueRead; i++)
	{
		pedido = (stPedido*)collection_list_get(threadFinded->QueueReads, i);
		for(j = 0; j <sizeListThreads ; j++)
		{
			threadNext = (stThreadPPD*)collection_list_get(raidDataGlobal.listadoThreads, i);
			if(threadNext->state == STATE_OPERATIVO)
				collection_list_add(threadNext->QueueReads, pedido);
		}
	}

	/* Broadcast de se�ales a los threads para que atiendan el nuevo pedido */
	for(j = 0; j <sizeListThreads ; j++)
	{
		threadNext = (stThreadPPD*)collection_list_get(raidDataGlobal.listadoThreads, i);
		if(threadNext->state == STATE_OPERATIVO)
			kill(threadNext->TID_thread, threadNext->signal);
	}

	return EXIT_SUCCESS;
}

int atenderSignalEspejar()
{
	/* Tengo que recorrer la lista de Threads y enviarle la se�al para espejar
	 * a todos los que tengan estado == ESPEJANDO_SLAVE *
	 */

	void signalToThreadParaEspejar(void* data)
	{
		stThreadPPD* thread = (stThreadPPD*)data;
		if(thread->state == ESPEJANDO_SLAVE)
			kill(thread->TID_thread, thread->signal);
	}
	collection_list_iterator(raidDataGlobal.listadoThreads, signalToThreadParaEspejar);
	return EXIT_SUCCESS;
}





